# Author: Allen Hutchison
# Copyright (C) 2006 Google Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#      Unless required by applicable law or agreed to in writing, software
#      distributed under the License is distributed on an "AS IS" BASIS,
#      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#      See the License for the specific language governing permissions and
#      limitations under the License.
#
# This Oaf package is responsible for finding, parsing, and managing suite files.
# Generally, you won't use this class by itself. Instead the Oaf::Harness class
# will use this one to get it's list of executables to run.

package Oaf::Suite;
use base qw(Oaf);

use strict;
use warnings;

use File::Copy qw(move);
use File::Temp qw(tempfile);

use Oaf::Test;

# new()
#  Create a new instance of an Oaf::Suite
#  This is used by the new() method in Oaf::Harness
#  Generally you would not create a new suite file
#  by itself, but instead use the access methods in the
#  harness. See Oaf::Harness::new() for more information.
sub new {
  my ($class, @args) = @_;

  # Start with a base Oaf object
  my $self = $class->SUPER::new(@args);
  # Turn it into a suite.
  bless $self, $class;

  # Find the next ready suite file (i.e. a file with no .running extension)
  # and parse it.
  $self->parse_suite($self->find_suite());

  return $self;
}

# find_suite()
#  Look in the queue directory for the next suite
#  file to be run. This is any suite file that doesn't
#  end in ".running". If there are more than one then
#  take the first one after sorting the list.
sub find_suite {
  my ($self) = @_;

  my $suite = $self->get_param('oaf', 'suite_file');

  if(defined($suite)) {
    $self->debug("Suite defined before find_suite called");
    return $suite;
  }


  # Base Path is defined when the config object is
  # initialized. See Oaf::Config for more info.
  my $base_path = $self->get_param('oaf', 'base_path');

  # TODO: Change these to File::Spec
  my $suite_queue = "$base_path/queue";

  # Find all the available suite files
  opendir(QUEUE, $suite_queue);
  my @files = grep { !/\.running|^\.|^README$/i } readdir(QUEUE);
  closedir(QUEUE);

  # Get the first suite file using default sort.
  $suite = (sort(@files))[0];

  if (defined($suite)) {
    $suite = "$suite_queue/$suite";
    $self->info("Suite File: $suite");
    return $self->set_param('oaf', 'suite_file', $suite);
  } else {
    # It's very possible that there won't be
    # a suite file to run.
    $self->info("No suite files in queue");
    return 0;
  }
}

# parse_suite()
#   Parse a suite file. Each command word in the suite file format
#   has a different handler. Normalize the command word, and then try
#   to find the correct handler for that word.
sub parse_suite {
  my($self, $suite) = @_;

  unless (defined($suite)) {
    $suite = $self->get_param('oaf', 'suite_file');
  }

  if (defined($suite) and -e $suite) {
    open SUITE, "$suite" or $self->fatal_error("Can't Open Suite File: $suite");

    while (<SUITE>) {
      $_ =~ s/^\s+|\s+$//g; # strip leading and trailing space
      $_ =~ m/^(\w+)\s+(.*)$/; # pull out the command
      my($command, $args) = ($1, $2);

      if (not $command) {
	$self->warning("", "Skipping invalid suite line: $_", "");
	next;
      }
      $command = lc($command);

      my $command_sub = "parse_suite_$command";
      if ($self->can($command_sub)) {
	$self->$command_sub($args);
      } else {
	$self->error("Don't know how to parse $command in suite file, skipping.");
      }
    }

  } else {
    $self->info("No suite file to process");
  }
}

# parse_suite_test()
#   Handler for any line in a suite file that begins with "test".
#   These lines all define tests that will be run during an automation
#   run.
sub parse_suite_test {
  my($self, $args) = @_;

  my $test = Oaf::Test->new($args);
  return($self->push_test($test));
}

# parse_suite_config()
#   Handler for any line in a suite file that begins with "config".
#   These lines all define configuration embedded in a suite file, and
#   use the same syntax as command line configuration.
sub parse_suite_config {
  my($self, $args) = @_;

  # Should match config in the standar format:
  # section.variable=value
  if ($args =~ /(\w+)\s*\.\s*(\w+)\s*=\s*(\S+)/) {
    return($self->set_param($1, $2, $3));
  } else {
    $self->error("Can't parse suite config line: $args");
  }
}

# mark_suite_running()
#   After a suite file has been parsed we mark it running in the queue
#   directory. This is mostly for the benefit of the user, so that he
#   can look in the queue directory to see what is running, and what is
#   waiting.
sub mark_suite_running {
  my($self) = @_;

  my $suite = $self->get_param('oaf', 'suite_file');
  if(defined($suite)) {
    move($suite, "$suite.running") or $self->fatal_error("Couldn't move $suite to $suite.running");
  }
}

# mark_suite_complete()
#   After the harness has completed a given run. The suite file in the
#   queue directory is deleted. This marks is as "complete".
sub mark_suite_complete {
  my($self) = @_;

  my $suite = $self->get_param('oaf', 'suite_file');
  if(defined($suite)) {
    if($self->get_param('oaf', 'delete_completed_suites', 0)) {
      unlink("$suite.running", $suite) or $self->error("Couldn't delete $suite.running");
    } else {
      move("$suite.running", "$suite.done") or $self->error("Couldn't move $suite.running to $suite.done");
    }
  }
}

# push_test()
#   The testing stack acts as a FIFO. This puts a test on the
#   stack.
sub push_test {
  my($self, $item) = @_;

  $self->debug("Pushing $item on test stack");
  push @{$self->{'test_stack'}}, $item;
}

# pop_test()
#   The testing stack acts as a FIFO. This shifts the next
#   test off of the stack.
sub pop_test {
  my($self) = @_;

  return(shift @{$self->{'test_stack'}});
}

# has_tests()
#   This returns the number of tests left on the test
#   stack. Will return 0 if the stack is empty or missing.
sub has_tests {
  my($self) = @_;

  if (defined @{$self->{'test_stack'}}) {
    return scalar @{$self->{'test_stack'}};
  } else {
    return 0;
  }
}

# Have to eval to something
1;
__END__
